<?php
/**
 * Created by PhpStorm.
 * User: pc
 * Date: 2024/4/9
 * Time: 11:01
 */

namespace app\common\service;


use app\common\enum\SetRssSitemapCrawler_statusEnum;
use app\common\enum\SetUrlCrawler_statusEnum;
use app\models\SetRssSitemapCrawler;
use app\models\SetUrlCrawler;
use PhpOffice\PhpSpreadsheet\Reader\Xml;
use yii\helpers\ArrayHelper;
use yii\helpers\Json;
use Yii;

class GptCrawler
{
    /**
     * @return boolean
     */
    private function test(){
        $output = shell_exec('echo 1');

        if ($output !== null) {
            return true;
        } else {
            return false;
        }
    }
    private function isTestRunning(){
        $output = shell_exec('ps -ef | grep  "sh -c docker exec gptout npm start 2>&1"| grep -v grep');
        if ($output !== null) {
            return true;
        } else {
            return false;
        }
    }
    private $file = "@app/config.js";

    /**
     * @return string
     */
    public function getFile()
    {
        return \Yii::getAlias($this->file);
    }

    /**
     * @param $file string
     * @return $this
     */
    public function setFile($file)
    {
        $this->file = $file;
        return $this;
    }

    /**
     * @param $row SetUrlCrawler
     * @return string
     */
    private function getJs($row){
        $match = $row->match;
        if(empty($match)){
            $match = $this->getHost($row->url)."/**";
        }
        $num = $row->max_pages_crawl;
        if($num <= 1){
            $num = 1;
        }
        $token = $row->max_tokens;
        if($token <= 100){
            $token = 100;
        }
        $js = <<<JS
export const defaultConfig = {
    url: "{$row->url}",
    match: "{$match}",
    maxPagesToCrawl: $num,
    outputFileName: "{$row->output_file_name}",
    maxTokens: $token,
};
JS;
    return $js;
    }

    private function getHost($url){
        $arr_url = parse_url($url);
        return "{$arr_url["scheme"]}://{$arr_url["host"]}";
    }

    public $output;

    private function execCommon(){
        //docker exec -it gptout npm start 2>&1 ，这是原命令
        //使用-it参数会导致crontab执行失败，crontab中执行时需要把交互参数-it去掉才能正常执行
        $command = "docker exec gptout npm start 2>&1";
        $output = shell_exec($command);
        $this->output = $output;
        echo $output;
        if(preg_match('/Finished/',$output)){
            return true;
        }
        return false;
    }

    private function testCreateDir($file){
        $dir_path = $this->getFileDir($file);
        $permissions = 0777;
        if(!is_dir($dir_path)){
            if(!mkdir($dir_path, $permissions, true)){
                return false;
            }

            if(!chmod($dir_path, $permissions)){
                return false;
            }
        }
        return true;
    }

    public function getFileDir($file){
        $path = \Yii::getAlias("@app/../outdata");
        $path = str_replace('outdata',$path,$file);
        $dir_path = dirname($path);
        return $dir_path;
    }
    /**
     * @param $row SetUrlCrawler
     * @return array
     */
    private function success($row){
        $directory = $this->getFileDir($row->output_file_name);
        $files = scandir($directory);
        $filteredFiles = array();
        $name = $this->getName($row->output_file_name);
        foreach ($files as $file) {
            // 排除目录本身和上级目录指示符
            if ($file != '.' && $file != '..') {
                // 检查文件扩展名是否匹配
                if (preg_match('/'.preg_quote($name).'/',$file)) {
                    // 如果是指定扩展名的文件，则添加到过滤后的文件列表中
                    $file_url = str_replace($name,$this->getName($file),$row->output_file_name);
                    $filteredFiles[] = $file_url;
                }
            }
        }
        return $filteredFiles;
    }

    public function getName($file){
        $arr_url = explode('/',$file);
        $name = $arr_url[count($arr_url) - 1];
        $name = str_replace('.json','',$name);
        return $name;
    }

    /**
     * @param $row SetUrlCrawler
     * @return bool
     */
    public function run($row){
        if($row->status === SetUrlCrawler_statusEnum::RUNNING){
            //已经有进程在执行，退出当前进程。
            return false;
        }
        echo "{$row->id}开始执行\n";
        if(!$this->test($row)){
            $row->status = SetUrlCrawler_statusEnum::FAIL;
            $row->err_msg = "shell_exec函数被禁用";
            $row->save();
            return false;
        }
        if($this->isTestRunning()){
            //已经有进程在执行，退出当前进程。
            return false;
        }
        $row->status = SetUrlCrawler_statusEnum::RUNNING;
        $row->save();


        if(!$this->testCreateDir($row->output_file_name)){
            $row->status = SetUrlCrawler_statusEnum::FAIL;
            $row->err_msg = "根据{$row->output_file_name}创建文件夹失败";
            $row->save();
            return false;
        }

        //配置文件路径
        $file = $this->getFile();
        $js = $this->getJs($row);
        $result = file_put_contents($file, $js);
        echo "{$row->id}写入配置\n";
        // 检查写入是否成功
        if ($result !== false) {
            echo "{$row->id}开始执行脚本\n";
            $ok = $this->execCommon();
            echo "{$row->id}采集结束{$ok}\n";
            if($ok){
                $arr_file = $this->success($row);
                $row->res_url = Json::encode($arr_file);
                $row->status = SetUrlCrawler_statusEnum::SUCCESS;
                $row->save();
                if($row->rss_sitemap_id > 0){
                    //更新采集时间
                    $row_rss = $row['rss'];
                    if($row_rss instanceof SetRssSitemapCrawler){
                        $row_rss->last_at = date("Y-m-d H:i:s");
                        $row_rss->save();
                    }
                }
                return true;
            }else{
                $row->status = SetUrlCrawler_statusEnum::FAIL;
                $row->err_msg = $this->output;
                $row->save();
            }
            return false;
        } else {
            $row->status = SetUrlCrawler_statusEnum::FAIL;
            $row->err_msg = "写入文件失败";
            $row->save();
            return false;
        }
    }

    public static function getOutFileName($url){
        $month = date("Ym");
        $name = substr(md5($url),8,16);
        return "outdata/t{$month}{$name}/f{$name}.json";
    }

    /**
     * @param $row SetUrlCrawler
     * @return bool
     */
    public function clear($row){
        //没有采集成功的，直接删除记录
        if($row->status !== SetUrlCrawler_statusEnum::SUCCESS){
            return $row->delete();
        }
        //采集成功的，需要删除对应文件
        $arr_file = $this->success($row);
        if($arr_file){

            foreach ($arr_file as $file){
                $dir = $this->getFileDir($file);
                $arr = explode("/",$file);
                $name = $arr[count($arr)-1];
                $path_file = "$dir/$name";
                if (file_exists($path_file)) {
                    if (unlink($path_file)) {
                        //echo 已删除文件
                    }
                }
            }
        }
        return $row->delete();
    }

    /**
     * @param $row SetRssSitemapCrawler
     * @return bool
     */
    public function rss($row){
        $url = $row->url;
        $xml_reader = new Xml();
        if($xml_reader){
            if($this->isRss($url)){
                $this->dealRss($row,$url,$xml_reader);
            }else{

            }
//            $xml_data = $xml_reader->trySimpleXMLLoadString($url);
//
//            var_dump($arr_data);exit();
        }
        return true;
    }

    /**
     * @param $row SetRssSitemapCrawler
     * @param $url
     * @param $xml_reader
     * @return bool
     * @throws \Exception
     */
    public function dealRss($row,$url,$xml_reader){
        $xml_data = $xml_reader->trySimpleXMLLoadString($url);
        $items = ArrayHelper::getValue($xml_data,"channel.item");
        $new_item = [];
        if(is_array($items)){
            foreach ($items as $item){
                if ($item instanceof \SimpleXMLElement){
                    $new_item[] = $this->dealRssXmlItem2Array($item);
                }
            }
            if($new_item){
                ArrayHelper::multisort($new_item,SORT_DESC);
                if(count($new_item) > $row->max_pages_crawl){
                    $new_item = array_slice($new_item,0,$row->max_pages_crawl);
                }
            }

        }elseif ($items instanceof \SimpleXMLElement){
            $new_item[] = $this->dealRssXmlItem2Array($items);
        }
        if($new_item){
            $start = strtotime("-{$row->day} day");
            $new_item = array_values(array_filter($new_item,function($item) use($start){
                return $item['time'] >= $start;
            }));
        }

        if($new_item){
            foreach ($new_item as $item){
                $this->dealRssXmlItem($item,$row->id);
            }
            $row->status = SetRssSitemapCrawler_statusEnum::SUCCESS;
            $row->last_at = date("Y-m-d H:i:s");
            $row->save();
            return true;
        }

        $row->status = SetRssSitemapCrawler_statusEnum::FAIL;
        $row->last_at = date("Y-m-d H:i:s");
        $row->save();
        return false;
    }

    public function dealRssXmlItem($item,$id){
        if(empty($item['link'])){
            return false;
        }
        $row = SetUrlCrawler::find()->andWhere(['url' => $item['link']])->one();
        if($row instanceof SetUrlCrawler){
            if($row->status == SetUrlCrawler_statusEnum::FAIL){
                $row->status = SetUrlCrawler_statusEnum::UNDO;
                $row->save();
                return true;
            }
            return false;
        }
        $row = new SetUrlCrawler();
        $row->url = $item['link'];
        $row->max_pages_crawl = 1;
        $row->match = "";
        $row->output_file_name = GptCrawler::getOutFileName($item['link']);
        $row->max_tokens = 2000000;
        $row->status = SetUrlCrawler_statusEnum::UNDO;
        $row->rss_sitemap_id = $id;
        $row->save();
    }

    public function dealRssXmlItem2Array($item){
        $new_item = [];
        $pubDate = ArrayHelper::getValue($item,'pubDate');
        $link =  ArrayHelper::getValue($item,'link');
        $new_item['time'] =strtotime($pubDate->__toString());
        $new_item['link'] = $link->__toString();
        return $new_item;
    }

    public function isRss($url){
        if(preg_match('/\.rss$/',$url)){
            return true;
        }
        return false;
    }
}